using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Web;

using NHibernate;
using NHibernate.Cfg;

namespace Awaken.Framework
{
    /// <summary>
    /// Represents a single database.
    /// </summary>
    public class Database
    {
        /// <summary>
        /// Key used to get and set the current NHibernate session.
        /// </summary>
        private static string CurrentSessionKey = "Awaken.Framework.Database.CurrentSession";
        
        /// <summary>
        /// NHibernate configuration object.
        /// </summary>
        private NHibernate.Cfg.Configuration _configuration = new NHibernate.Cfg.Configuration();
        
        /// <summary>
        /// NHibernate session factory.
        /// </summary>
        private NHibernate.ISessionFactory _sessionFactory;
                
        /// <summary>
        /// Current NHibernate transaction.
        /// </summary>
        private ITransaction _transaction;
        
        /// <summary>
        /// The name of the database.
        /// </summary>
        private string _name;
        
        /// <summary>
        /// The database connection string.
        /// </summary>
        private string _connectionString;
        
        /// <summary>
        /// Indicates if the database has been configured via AwakenSetup.config.
        /// </summary>
        private bool _isConfigured;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public Database(){}
          
        /// <summary>
        /// Gets or sets the name of the database.
        /// </summary>
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
          
        /// <summary>
        /// Gets or sets the database connection string.
        /// </summary>
        public string ConnectionString
        {
            get { return _connectionString; }
            set { _connectionString = value; }
        }
        
        /// <summary>
        /// Gets or sets the value indicating if the database has been
        /// configured.
        /// </summary>
        public bool IsConfigured
        {
            get { return _isConfigured; }
            set { _isConfigured = value; }
        }

        /// <summary>
        /// Begins a new transaction.
        /// </summary>
        public void BeginTransaction()
        {
            if (_transaction == null)
            {
                _transaction = this.CurrentSession.BeginTransaction();
            }            
        }

        /// <summary>
        /// Commits the current transaction, if one exists.
        /// </summary>
        public void CommitTransaction()
        {
            if (_transaction != null)
            {
                _transaction.Commit();
            }
        }

        /// <summary>
        /// Aborts the current transaction, if one exists, and clears the current session.
        /// </summary>
        public void RollbackTransaction()
        {
            if (_transaction != null)
            {
                _transaction.Rollback();
                CurrentSession.Clear();
            }
        }

        /// <summary>
        /// Gets the NHibernate session factory.
        /// </summary>
        public ISessionFactory SessionFactory
        {
            get
            {
                if (!IsConfigured)
                {
                    throw new AwakenException(Messages.NotConfigured);
                }

                if (_sessionFactory == null)
                {
                    _sessionFactory = Configuration.BuildSessionFactory();
                }

                return _sessionFactory;
            }
        }
        
        /// <summary>
        /// Gets the current NHibernate session.  If there is an HttpContext
        /// available the current session is stored there, otherwise it is stored
        /// to the static session variable.
        /// </summary>
        /// <remarks>
        /// Uses System.Runtime.Remoting.Messaging.CallContext to store the 
        /// session information -- per this posting on the NHibernate forum:
        /// http://forum.hibernate.org/viewtopic.php?t=951984&highlight=callcontext
        /// 
        /// Quote:"Perhaps a better choice is to use the System.Runtime.Remoting.Messaging.CallContext 
        /// class instead of TLS. This is the class that is used internally by ASP.NET to store the HTTP 
        /// context object anyway. This can apparently be verified by using Reflector to view the HttpContext 
        /// implementation."
        /// </remarks>
        public ISession CurrentSession
        {
            get
            {                
                if( CallContext.GetData(CurrentSessionKey)==null )
                {
                    CallContext.SetData(CurrentSessionKey, SessionFactory.OpenSession());
                }
                return CallContext.GetData(CurrentSessionKey) as ISession;
            }
            set
            {
                CallContext.SetData(CurrentSessionKey,value);
            }
        }

        /// <summary>
        /// Closes the current NHibernate session.  Closes any open transactions.
        /// </summary>
        public void CloseSession()
        {
            //TODO: Do we want to abort any open transactions, rather than commit?
            CommitTransaction();

            CurrentSession.Close();
            CurrentSession.Dispose();
            CurrentSession = null;
        }

        /// <summary>
        /// Gets or sets the NHibernate configuration object.
        /// </summary>
        public NHibernate.Cfg.Configuration Configuration
        {
            get { return _configuration; }
            set { _configuration = value; }
        }

        /// <summary>
        /// Gets a value indicating if the given type is stored in this database.
        /// </summary>
        /// <param name="type">Type being checked.</param>
        /// <returns>Value indicating if the type is contained within.</returns>
        /// <remarks>
        /// Each type mapping should have a metadata node with the name of its
        /// containing database.
        /// </remarks>
        public bool Contains(System.Type type)
        {
            bool contains = false;

            NHibernate.Mapping.PersistentClass clazz = this.Configuration.GetClassMapping(type);

            NHibernate.Mapping.MetaAttribute attribute = clazz.GetMetaAttribute("Database");

            if (attribute != null)
            {
                if (attribute.Value == this.Name)
                {
                    contains = true;
                }
            }

            return contains;
        }

        /// <summary>
        /// Saves the given entity.
        /// </summary>
        /// <param name="obj"></param>
        public void Save(object entity)
        {
            try
            {
                CurrentSession.Save(entity);
            }
            catch
            {
                throw;
            }
            finally
            {
                CurrentSession.Flush();
            }
        }

        /// <summary>
        /// Deletes the given entity.
        /// </summary>
        /// <param name="entity"></param>
        public void Delete(object entity)
        {
            try
            {
                CurrentSession.Delete(entity);
            }
            catch
            {
                throw;
            }
            finally
            {
                CurrentSession.Flush();
            }
        }

        /// <summary>
        /// Returns an object of the given type with the given ID.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        public object GetByID(System.Type type, object id)
        {
            object obj = null;

            try
            {
                // TODO: Deal with objects with compound keys.
                obj = CurrentSession.Get(type, id);
            }
            catch
            {
                throw;
            }
            finally
            {
                //TODO: Verify that we want to flush the session.
                CurrentSession.Flush();
            }
            return obj;
        }

        /// <summary>
        /// Returns all instances of the given type.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public IList GetAll(System.Type type)
        {
            if (type == null)
            {
                // TODO: Throw descriptive exception.
                return null;
            }

            IList list = null;

            string sql = string.Format(CultureInfo.InvariantCulture,@"SELECT obj FROM {0} obj", type.Name);

            try
            {
                list = CurrentSession.Find(sql);
            }
            catch
            {
                throw;
            }
            finally
            {
                //TODO: Verify that we want to flush the session.
                CurrentSession.Flush();
            }

            return list;
        }

        /// <summary>
        /// Returns all types mapped to this database.
        /// </summary>
        /// <returns></returns>
        public Collection<Type> MappedTypes
        {
            get
            {
                Collection<Type> types = new Collection<Type>();

                foreach (NHibernate.Mapping.RootClass clazz in Configuration.ClassMappings)
                {
                    types.Add(clazz.MappedClass);
                }

                return types;
            }
        }
    }
}
